<?php
/**
 * @copyright  Copyright  LexisNexis, a division of Reed Elsevier Inc. All rights reserved.
 */
namespace sdk;

use UsageConst;

require_once(__DIR__ . '/vendor/autoload.php');
include_once(__DIR__ . '/config.php');
include_once(__DIR__ . '/Browser.php');
include_once(__DIR__ . '/QueuePublisher.php');

class UsageClient {
    private static $obj;
    private static $error = 0;
    public static function instance() {
        if (!self::$obj) {
            self::$obj = new self();
        }
        return self::$obj;
    }

    private function __construct() {
        return;
    }

    public function createUUID($prefix = "") {
        $str = md5(uniqid(mt_rand(), true));
        $uuid  = substr($str,0,8) . '-';
        $uuid .= substr($str,8,4) . '-';
        $uuid .= substr($str,12,4) . '-';
        $uuid .= substr($str,16,4) . '-';
        $uuid .= substr($str,20,12);

        return $prefix . $uuid;
    }

    public function outputRewriteVars($crid) {
        $prid = $crid;
        $crid = $this->createUUID();

        return array('prid' => $prid, 'crid' => $crid);
    }

    public function replaceUrl($url, $prid = "", $crid = "", $addition = array(), $anchor = "") {
        $params = array();
        $uri = $url;
        if (empty($prid)) {
            $prid = $this->getPrid();
        }
        if (empty($crid)) {
            $crid = $this->getCrid();
        }

        if ($this->isAjax() && empty($prid) && empty($crid)) {
            $ids = $this->getReferrerPridCrid();
            $ids = $this->outputRewriteVars($ids['crid']);
            $prid = $ids['prid'];
            $crid = $ids['crid'];
        }

        $query = parse_url($url, PHP_URL_QUERY);
        if ($query) {
            $uri = substr($url, 0, strlen($url) - strlen($query) - 1);
            parse_str($query, $params);
        }

        if ($prid) {
            $params['prid'] = $prid;
        }
        if ($crid) {
            $params['crid'] = $crid;
        }
        $params = array_merge($params, $addition);

        $special = array();
        $newQuery = '';
        if (!empty($params)) {
            foreach ($params as $key => $value) {
                if (is_string($value) && strlen($value) == 0) {
                    $special[] = $key;
                }
            }
            $newQuery = http_build_query($params);
            if ($newQuery) {
                $newQuery = '?' . $newQuery;
            }

            foreach ($special as $val) {
                if (stripos($query, $val . '=') === false) {
                    $newQuery = str_ireplace($val . '=', $val, $newQuery);
                }
            }
        }

        return $uri . $newQuery . $anchor;
    }

    public function replaceHref($url) {
        return $this->replaceUrl($url, '', '', array(), UsageConst::JUMP_FLAG);
    }

    public function expandUrl($url) {
        $urlArray = parse_url($url);
        if (!is_array($urlArray)) {
            return array();
        }

        if (!empty($urlArray['host'])) {
            $urlArray['_host'] = explode('.', $urlArray['host']);
        }
        if (!empty($urlArray['path'])) {
            $urlArray['_path'] = explode('/', $urlArray['path']);
        }
        if (!empty($urlArray['query'])) {
            $query = array();
            parse_str($urlArray['query'], $query);
            $urlArray['_query'] = is_array($query) ? $query : array();
        }

        return $urlArray;
    }

    public function getClassName($url) {
        $uris = parse_url($url);

        // 1. /law/loginlogic.php
        // 2. /
        // 3. /topic/
        $path = "index";
        if (!empty($uris['path']) && $uris['path'] != '/') {
            if ($uris['path']{0} == '/') {
                $path = substr($uris['path'], 1);
            }
            $len = strlen($path);
            if ($path{$len - 1} == '/') {
                $path = substr($path, 0, $len - 1);
            }
            $path = strtolower($path);
            $path = str_ireplace('.php', '', $path);
            $path = str_ireplace('/', '.', $path);
            //topic.landing
        }

        $params = array();
        if (!empty($uris['query'])) {
            parse_str($uris['query'], $params);
        }

        $ext = "";
        if (isset(UsageConst::$EVENT_NAME_MAPPING[$path])) {
            $args = UsageConst::$EVENT_NAME_MAPPING[$path];
            $map = array();
            foreach ($args as $arg) {
                if (isset($params[$arg])) {
                    $map[] = $arg . '_' . $params[$arg];
                }
            }
            if (sizeof($map) > 0) {
                $ext = implode('.', $map);
            }
            //tps_ep
        }

        $name = $path;
        if ($ext) {
            $name = $name . '.' . $ext;
        }

        return $name;
    }

    protected function getUrl() {
        if (empty($_SERVER['SERVER_NAME'])) {
            $_SERVER['SERVER_NAME'] = '127.0.0.1';
        }
        if (empty($_SERVER['REQUEST_URI'])) {
            $_SERVER['REQUEST_URI'] = '/';
        }
        if (empty($_SERVER['SERVER_PORT'])) {
            $_SERVER['SERVER_PORT'] = '80';
        }
        if (empty($_SERVER['SERVER_PROTOCOL'])) {
            $_SERVER['SERVER_PROTOCOL'] = 'http/1.0';
        }
        $ssl = false;
        if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') {
            $_SERVER['HTTPS'] = 'on';
            $ssl = true;
        }

        $port = $_SERVER['SERVER_PORT'];
        if ((!$ssl && $port == '80') || ($ssl && $port == '443')) {
            $port = '';
        }

        $protocol = explode('/', $_SERVER['SERVER_PROTOCOL']);
        $protocol = strtolower($protocol[0]);
        if ($ssl) {
            $protocol .= 's';
        }

        $host = isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST']
                : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null);
        $host = $host ? $host : $_SERVER['SERVER_NAME'] . $port;

        return $protocol . '://' . $host . $_SERVER['REQUEST_URI'];
    }

    protected function getCurrentTime() {
        return date('Y-m-d H:i:s');
    }

    protected function getUID() {
        return !empty($_SESSION['chonUser']['userid']) ? $_SESSION['chonUser']['userid'] : 0;
    }

    protected function getUserAgent() {
        return !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
    }

    protected function getReferrer() {
        return !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
    }

    protected function getServerIP() {
        return !empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '';
    }

    public function isAjax() {
        return isset($_SERVER['HTTP_X_REQUESTED_WITH'])
            && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
    }

    public function isSpider($useragent = '') {
        $kw_spiders = array('bot', 'crawl', 'spider' ,'slurp', 'sohu-search', 'lycos', 'robozilla');
        $kw_browsers = array('msie', 'netscape', 'opera', 'konqueror', 'mozilla');

        $useragent = strtolower(empty($useragent) ? $this->getUserAgent() : $useragent);
        if (strpos($useragent, 'http://') === false && $this->dstrpos($useragent, $kw_browsers)) {
            return false;
        }

        if ($this->dstrpos($useragent, $kw_spiders)) {
            return true;
        }

        return false;
    }

    protected function dstrpos($string, $arr, $returnvalue = false) {
        if (empty($string)) {
            return false;
        }

        foreach ((array)$arr as $v) {
            if (strpos($string, $v) !== false) {
                $return = $returnvalue ? $v : true;
                return $return;
            }
        }

        return false;
    }

    protected function getSessionID() {
        $sid = session_id();
        return !empty($sid) ? $sid : '';
    }

    protected function getRealSession() {
        if (empty($_COOKIE[UsageConst::SESSION_NAME])) {
            return '';
        }

        $session = explode('_', $_COOKIE[UsageConst::SESSION_NAME]);
        if (empty($session[1])) {
            return '';
        }

        return (string) $session[1];
    }

    protected function getSessionTime() {
        if (empty($_COOKIE[UsageConst::SESSION_NAME])) {
            return 0;
        }

        $session = explode('_', $_COOKIE[UsageConst::SESSION_NAME]);
        if (empty($session[0])) {
            return 0;
        }

        return (int) $session[0];
    }

    public function getPrid() {
        if (empty($_REQUEST['prid'])) {
            return '';
        }
        if (is_array($_REQUEST['prid']) && sizeof($_REQUEST['prid']) > 0) {
            return (string) array_pop($_REQUEST['prid']);
        }

        return (string) $_REQUEST['prid'];
    }

    public function getCrid() {
        if (empty($_REQUEST['crid'])) {
            return '';
        }
        if (is_array($_REQUEST['crid']) && sizeof($_REQUEST['crid']) > 0) {
            return (string) array_pop($_REQUEST['crid']);
        }

        return (string) $_REQUEST['crid'];
    }

    public function getReferrerPridCrid() {
        $ids = array('prid' => '', 'crid' => '');

        $referrer = $this->getReferrer();
        if (empty($referrer)) {
            return $ids;
        }

        $query = parse_url($referrer, PHP_URL_QUERY);
        if (empty($query)) {
            return $ids;
        }

        $params = array();
        parse_str($query, $params);
        if (!empty($params['prid'])) {
            if (is_array($params['prid']) && sizeof($params['prid']) > 0)  {
                $ids['prid'] = (string) array_pop($params['prid']);
            } else {
                $ids['prid'] = (string) $params['prid'];
            }
        }
        if (!empty($params['crid'])) {
            if (is_array($params['crid']) && sizeof($params['crid']) > 0)  {
                $ids['crid'] = (string) array_pop($params['crid']);
            } else {
                $ids['crid'] = (string) $params['crid'];
            }
        }

        return $ids;
    }

    public function getEnvironment() {
        return isset($_COOKIE['lang']) ? trim($_COOKIE['lang']) : 'unknown';
    }

    public function getBrowserInfo() {
        $browser = new \Browser();
        return array(
            'name'      => $browser->getBrowser(),
            'version'   => $browser->getVersion(),
            'platform'  => $browser->getPlatform(),
            'is_mobile' => $browser->isMobile(),
            'is_robot'  => $browser->isRobot(),
            'is_tablet' => $browser->isTablet(),
        );
    }

    private function getLogHead() {
        $url = $this->getUrl();
        $referrer = $this->getReferrer();
        $browser = $this->getBrowserInfo();

        return array(
            'uid'           => (int) $this->getUID(),
            'user_agent'    => $this->getUserAgent(),
            'client_ip'     => $this->getIpAddress(),
            'server_ip'     => $this->getServerIP(),
            'session_id'    => $this->getSessionID(),
            'real_session'  => $this->getRealSession(),
            'session_time'  => $this->getSessionTime(),
            'prid'          => $this->getPrid(),
            'crid'          => $this->getCrid(),
            'url'           => $url,
            'class'         => $this->getClassName($url),
            'create_time'   => $this->getCurrentTime(),
            'timestamp'     => time(),
            'is_js_event'   => (int) ($this->isAjax()),
            'environment'   => $this->getEnvironment(),
            'browser_name'  => $browser['name'],
            'browser_version'  => $browser['version'],
            'platform'      => $browser['platform'],
            'is_mobile'     => (int) $browser['is_mobile'],
            'is_tablet'     => (int) $browser['is_tablet'],
        );
    }

    protected function getServer() {
        $somethingUseful = array(
            'USER', 'HOME', 'FCGI_ROLE', 'HTTPS', 'GATEWAY_INTERFACE',
            'SERVER_SOFTWARE', 'QUERY_STRING', 'REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH',
            'SCRIPT_FILENAME', 'SCRIPT_NAME', 'REQUEST_URI', 'DOCUMENT_URI', 'DOCUMENT_ROOT',
            'SERVER_PROTOCOL', 'REMOTE_ADDR', 'REMOTE_PORT', 'SERVER_ADDR', 'SERVER_PORT', 'SERVER_NAME',
            'REDIRECT_STATUS', 'HTTP_HOST', 'HTTP_CONNECTION', 'HTTP_CONTENT_LENGTH', 'HTTP_ACCEPT',
            'HTTP_ORIGIN', 'HTTP_X_REQUESTED_WITH', 'HTTP_USER_AGENT', 'HTTP_CONTENT_TYPE',
            'HTTP_REFERER', 'HTTP_ACCEPT_ENCODING', 'HTTP_ACCEPT_LANGUAGE', 'HTTP_COOKIE',
            'PHP_SELF', 'REQUEST_TIME'
        );
        $server = array();
        foreach ($somethingUseful as $useful) {
            if (isset($_SERVER[$useful])) {
                $server[$useful] = $_SERVER[$useful];
            }
        }
        return $server;
    }

    private function getLogVars() {
        return array(
            'get'       => $this->encodeObject($_GET),
            'post'      => $this->encodeObject($_POST),
            'cookie'    => $_COOKIE,
            'session'   => $this->encodeObject($_SESSION),
            'server'    => $this->getServer(),
            'files'     => $this->encodeObject($_FILES)
        );
    }

    protected function encodeObject($object) {
        return base64_encode(json_encode($object));
    }

    public function log($product, $name, $body = array(), $type = UsageConst::TYPE_OTHER) {
        //error_reporting(E_ALL);
        if ($this->isSpider()) {
            return true;
        }
        @session_start();

        $head = $this->getLogHead();
        $head['product'] = $product;
        $head['name'] = $name;
        $head['type'] = $type;

        if (!empty($body['prid'])) {
            $head['prid'] = $body['prid'];
            unset($body['prid']);
        }
        if (!empty($body['crid'])) {
            $head['crid'] = $body['crid'];
            unset($body['crid']);
        }
        if (!empty($body['class'])) {
            $head['class'] = $body['class'];
            unset($body['class']);
        }

        if (!is_array($body)) {
            $body = array();
        }
        $logData = array(
            'head' => $head,
            'body' => $body,
            //'vars' => $this->getLogVars()
        );
        $logData['hash'] = md5(json_encode($logData));

        if (!empty($_SESSION[UsageConst::EVENT_ERROR]) && !empty($_SESSION[UsageConst::EVENT_TIME])) {
            if (time() - $_SESSION[UsageConst::EVENT_TIME] < UsageConst::ERROR_MIN_TIME) {
                return false;
            }
        }
        $ret = $this->pushQueue($logData);
        if (!$ret) {
            if (isset($_SESSION[UsageConst::EVENT_ERROR])) {
                $_SESSION[UsageConst::EVENT_ERROR]++;
            } else {
                $_SESSION[UsageConst::EVENT_ERROR] = 1;
            }
            $_SESSION[UsageConst::EVENT_TIME] = time();
        }

        return $ret;
    }

    protected function pushQueue($logData) {
        if (self::$error > 0) {
            return false;
        }
        try {
            QueuePublisher::instance()->push($logData);
        } catch (\Exception $exception) {
            self::$error++;
            // @todo must report this warning!
            return false;
        }

        return true;
    }

    protected function httpPost($url, $data, $options = array()) {
        $defaults = array(
            CURLOPT_POST => 1,
            CURLOPT_HEADER => 0,
            CURLOPT_URL => $url,
            CURLOPT_FRESH_CONNECT => 1,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_FORBID_REUSE => 1,
            CURLOPT_TIMEOUT => 4,
            CURLOPT_POSTFIELDS => http_build_query($data)
        );
        $ch = curl_init();
        curl_setopt_array($ch, $options + $defaults);
        if (!$result = curl_exec($ch)) {
            //trigger_error(curl_error($ch));
            return false;
        }
        curl_close($ch);
        return $result;
    }

    protected function getProxyIpAddress() {
        if (empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            return false;
        }

        // Extract IPs
        $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        // trim, so we can compare against trusted proxies properly
        $ips = array_map('trim', $ips);
        // remove trusted proxy IPs
        $ips = array_unique($ips);

        // Any left?
        if (empty($ips)) {
            return false;
        }

        // Since we've removed any known, trusted proxy servers, the right-most
        // address represents the first IP we do not know about -- i.e., we do
        // not know if it is a proxy server, or a client. As such, we treat it
        // as the originating IP.
        // @see http://en.wikipedia.org/wiki/X-Forwarded-For
        $ip = array_pop($ips);
        return $ip;
    }

    protected function getIpAddress() {
        // check for shared internet/ISP IP
        if (!empty($_SERVER['HTTP_CLIENT_IP']) && $this->validateIP($_SERVER['HTTP_CLIENT_IP'])) {
            return $_SERVER['HTTP_CLIENT_IP'];
        }

        // check for IPs passing through proxies
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            // check if multiple ips exist in var
            if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false) {
                $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
                foreach ($iplist as $ip) {
                    if ($this->validateIP($ip))
                        return $ip;
                }
            } else {
                if ($this->validateIP($_SERVER['HTTP_X_FORWARDED_FOR']))
                    return $_SERVER['HTTP_X_FORWARDED_FOR'];
            }
        }
        if (!empty($_SERVER['HTTP_X_FORWARDED']) && $this->validateIP($_SERVER['HTTP_X_FORWARDED']))
            return $_SERVER['HTTP_X_FORWARDED'];
        if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && $this->validateIP($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
            return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
        if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && $this->validateIP($_SERVER['HTTP_FORWARDED_FOR']))
            return $_SERVER['HTTP_FORWARDED_FOR'];
        if (!empty($_SERVER['HTTP_FORWARDED']) && $this->validateIP($_SERVER['HTTP_FORWARDED']))
            return $_SERVER['HTTP_FORWARDED'];

        // return unreliable ip since all else failed
        return !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
    }

    /**
     * Ensures an ip address is both a valid IP and does not fall within
     * a private network range.
     */
    protected function validateIP($ip) {
        if (strtolower($ip) === 'unknown') {
            return false;
        }

        // generate ipv4 network address
        $ip = ip2long($ip);

        // if the ip is set and not equivalent to 255.255.255.255
        if ($ip !== false && $ip !== -1) {
            // make sure to get unsigned long representation of ip
            // due to discrepancies between 32 and 64 bit OSes and
            // signed numbers (ints default to signed in PHP)
            $ip = sprintf('%u', $ip);
            // do private network range checking
            if ($ip >= 0 && $ip <= 50331647) return false;
            if ($ip >= 167772160 && $ip <= 184549375) return false;
            if ($ip >= 2130706432 && $ip <= 2147483647) return false;
            if ($ip >= 2851995648 && $ip <= 2852061183) return false;
            if ($ip >= 2886729728 && $ip <= 2887778303) return false;
            if ($ip >= 3221225984 && $ip <= 3221226239) return false;
            if ($ip >= 3232235520 && $ip <= 3232301055) return false;
            if ($ip >= 4294967040) return false;
        }
        return true;
    }
}
